#include "myD3D.h"

// constructor & destructor
myD3D::myD3D() {
	d3d = NULL;
	d3ddev = NULL;
	memset(&d3dpp, 0, sizeof(D3DPRESENT_PARAMETERS));
	hWnd = NULL;
	rootFramesCount = 0;
	rootFrames = NULL;
    ani = NULL;
}
myD3D::~myD3D(){

	// release all directX objects
	SAFE_RELEASE(d3ddev);
	SAFE_RELEASE(d3d);
}

// a MUST finction to initialize directX properly and assign it to a window
//	_hWnd - handle to a window
//	fullscreen - do we want fullscreen?
//	w,h - width and height of the window
bool myD3D::Init(HWND _hWnd, bool fullscreen, int w, int h){
	// if directX is not alreade initialzed ...
	if (NULL != d3d) return false;

	// create directX instance - ver 9
	d3d = Direct3DCreate9(D3D_SDK_VERSION);
	if (!d3d) return false;

	// clear out structure and fill its necessary fields
	ZeroMemory(&d3dpp, sizeof(D3DPRESENT_PARAMETERS));
	d3dpp.Windowed = !fullscreen;				// do we want fullscreen
	d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;	// set back buffer and forget old frames
	d3dpp.hDeviceWindow = _hWnd;				// set the window handle
	d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8;	// set the back buffer format to xRGB (32-bit)
	d3dpp.BackBufferHeight = h;					// set the height of the back buffer
	d3dpp.BackBufferWidth = w;					// set the width of the back buffer
	d3dpp.AutoDepthStencilFormat = D3DFMT_D16;	// 16-bit z-buffer type
	d3dpp.EnableAutoDepthStencil = true;		// let the directX take care of z-buffer

	// try to init directX Device in defalut adapter and as much hardwere involved as possible
	if (S_OK != d3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, _hWnd, 
								  D3DCREATE_HARDWARE_VERTEXPROCESSING, &d3dpp, &d3ddev)) return false;

	// setup camera 
	setLookAt();
	// setup viewing perspective
	setPerspective();

	// set up the point light in camera
	D3DLIGHT9 light;
	ZeroMemory(&light, sizeof(D3DLIGHT9));
	light.Type = D3DLIGHT_DIRECTIONAL; //D3DLIGHT_POINT;
	light.Ambient.a = light.Diffuse.a = 1.0f;
	light.Ambient.r = 0.0f; light.Diffuse.r = 0.6f;
	light.Ambient.g = 0.0f; light.Diffuse.g = 0.6f;
	light.Ambient.b = 0.0f; light.Diffuse.b = 0.6f;
	light.Direction = D3DXVECTOR3(0.0f, 0.0f, -1.0f);

	setLight(0, true, &light);

	// enable z-buffer and add some ambient light
	d3ddev->SetRenderState(D3DRS_ZENABLE, true);
	d3ddev->SetRenderState(D3DRS_AMBIENT, D3DCOLOR_XRGB(20, 20, 20));

	// normalize normals ( ||n||=1 )
	d3ddev->SetRenderState(D3DRS_NORMALIZENORMALS, TRUE);

	// Turn off culling
    d3ddev->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);

	// enable blending
	d3ddev->SetRenderState(D3DRS_ALPHABLENDENABLE, true);
	d3ddev->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
	d3ddev->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
	d3ddev->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD);
	
	// if successfully initialized remember the window handle
	hWnd = _hWnd;
	return true;
}

// set LookAt
bool myD3D::setLookAt(){
	D3DXMATRIX look;
	D3DXMatrixLookAtLH(&look, &D3DXVECTOR3(0.0f, 5.0f, 20.0f),	// camera position
							  &D3DXVECTOR3(0.0f, 0.0f, 0.0f),	// the lookAt position
							  &D3DXVECTOR3(0.0f, 1.0f, 0.0f));	// the up direction		
	return setLookAt(&look);
}
// 2nd version of setLookAt() using 3 3DVectors (camera, lookAtPoint, upPos)
bool myD3D::setLookAt(D3DXVECTOR3 *camPosition, D3DXVECTOR3 *lookAtPosition, D3DXVECTOR3 *upDirection){
	D3DXMATRIX look;
	D3DXMatrixLookAtLH(&look, camPosition, lookAtPosition, upDirection);
	return setLookAt(&look);
}
// 3rd version of setLookAt() using 4x4 view transform matrix
bool myD3D::setLookAt(D3DXMATRIX *view){
	if (S_OK != d3ddev->SetTransform(D3DTS_VIEW, view)) return false;
	return true;
}

// simplest version of setPerspective() - used in ::Init()
bool myD3D::setPerspective(){
	D3DXMATRIX pers;
	D3DXMatrixPerspectiveFovLH(&pers, D3DXToRadian(45.0f), 
		(float)d3dpp.BackBufferWidth/(float)d3dpp.BackBufferHeight, 0.1f, 1000.0f);
	return setPerspective(&pers);
}
// 2d version of setPerspective() - changes perspective according to user input
//	fovRadian - angle Field Of View (in radians)
//	aspect_WOverH - aspect=width/height
//	z_near, z_far - nearest and furthest clipping plane
bool myD3D::setPerspective(float fovRadian, float aspect_WOverH, float z_near, float z_far){
	D3DXMATRIX pers;
	D3DXMatrixPerspectiveFovLH(&pers, fovRadian, aspect_WOverH, z_near, z_far);
	return setPerspective(&pers);
}
// 3rd version of setPerspective() where projection matrix is passed as a parameter
bool myD3D::setPerspective(D3DXMATRIX *projectionMatrix){
	if(S_OK != d3ddev->SetTransform(D3DTS_PROJECTION, projectionMatrix)) return false;
	return true;
}

//setup light in the scene
void myD3D::setLight(DWORD index, bool makeOn, D3DLIGHT9 *light){
	if (makeOn){		// turning the light on
		d3ddev->SetLight(index, light);
		d3ddev->LightEnable(index, makeOn);
	}else{				// turning the light off
		d3ddev->LightEnable(index, makeOn);
	}
}

// set on/off 3D lighting
void myD3D::light3d(bool on){
	setRendState(D3DRS_LIGHTING, on);
}

// change renderer state to coustom one
bool myD3D::setRendState(D3DRENDERSTATETYPE state, DWORD value){
	if (S_OK != d3ddev->SetRenderState(D3DRS_LIGHTING, value)) return false;
	return true;
}

// load SkinnedMesh
HRESULT myD3D::loadSkin(LPCWSTR path) {
	LPD3DXFRAME *tfr = new LPD3DXFRAME [rootFramesCount++];
	for (UINT i = 0; i < (rootFramesCount-1); i++)
		tfr[i] = rootFrames[i];
	SAFE_DELETE_ARRAY(rootFrames);
	rootFrames = tfr;
	myAllocHierarchy mh;
	HRESULT hr = D3DXLoadMeshHierarchyFromX(path, D3DXMESH_MANAGED, d3ddev, &mh, NULL, &rootFrames[rootFramesCount-1], &ani);
	return hr;
}

struct Vertex {
	float x, y, z;
	DWORD color;
};
struct vert_nor
{
   FLOAT X, Y, Z;
   D3DXVECTOR3 n;
};
static float angle = 0;
void myD3D::drawFrame(LPD3DXFRAME frame){

	my_D3DXMESHCONTAINER *mc = (my_D3DXMESHCONTAINER*) frame->pMeshContainer;
	if (mc) {
/*		
		// to del
		int x = 1;
		D3DXMATRIX mm;
		D3DXMatrixIdentity(&mm);
		mm._42 = -(*mc->pSkinInfo->GetBoneOffsetMatrix(1))._41;
		mm._41 = (*mc->pSkinInfo->GetBoneOffsetMatrix(1))._42;
		mm._43 = (*mc->pSkinInfo->GetBoneOffsetMatrix(1))._43;
		//d3ddev->SetTransform(D3DTS_WORLD, &(mm));
		//D3DXMatrixRotationZ(&(mc->pBoneOffsetMatrices[x]), D3DXToRadian(angle));
		//mc->pBoneOffsetMatrices[x] =  mm;// * mc->pBoneOffsetMatrices[x];// * mm;
		angle += 1.0f;		
		Vertex v[] = {{(*mc->pSkinInfo->GetBoneOffsetMatrix(x))._42,
			           -(*mc->pSkinInfo->GetBoneOffsetMatrix(x))._41,
					   (*mc->pSkinInfo->GetBoneOffsetMatrix(x))._43-10,
					   D3DCOLOR_XRGB(0,0,0)},
					  {(*mc->pSkinInfo->GetBoneOffsetMatrix(x))._42,
			           -(*mc->pSkinInfo->GetBoneOffsetMatrix(x))._41,
					   (*mc->pSkinInfo->GetBoneOffsetMatrix(x))._43+10,
					   D3DCOLOR_XRGB(0,0,0)}};
		d3ddev->SetFVF(D3DFVF_XYZ | D3DFVF_DIFFUSE);
		d3ddev->DrawPrimitiveUP(D3DPT_LINESTRIP, 1, &v, sizeof(Vertex));
		// to del - end
*/
		PVOID src, dst;
		mc->pOrigMesh->LockVertexBuffer(D3DLOCK_READONLY, (LPVOID*) &src);
		mc->MeshData.pMesh->LockVertexBuffer(0, (LPVOID*) &dst);
		/*	IMPORTANT NOTE:
		 *	UpdateSkinInfo ONLY	affects vertices CONNECTED 
		 *	WITH THE BONES NO MATTER OF SKINWEIGHTS !!!
		 *	IF vertex has no connection with bone
		 *	it WILL NOT BE UPDATED !!!
		 */
		mc->pSkinInfo->UpdateSkinnedMesh(mc->pBoneOffsetMatrices, NULL, src, dst);
		mc->pOrigMesh->UnlockVertexBuffer();
		mc->MeshData.pMesh->UnlockVertexBuffer();

		d3ddev->SetMaterial(&mc->pMaterials[0].MatD3D);
		mc->MeshData.pMesh->DrawSubset(0);
	}

	if (frame->pFrameFirstChild)
		drawFrame(frame->pFrameFirstChild);

	if (frame->pFrameSibling)
		drawFrame(frame->pFrameSibling);
}


// render the frame
void myD3D::RenderFrame(){
	// clear color buffer & z-buffer
	d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(100, 100, 255), 1.0f, 0);
	d3ddev->Clear(0, NULL, D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);
	d3ddev->BeginScene();	// start drawing the scene

	//D3DXMATRIX m, n;
	//d3ddev->GetTransform(D3DTS_VIEW, &m);
	//d3ddev->GetTransform(D3DTS_PROJECTION, &n);

	// set camera and viewport properties
	setLookAt();
	setPerspective();

	D3DXMATRIX mx;
	D3DXMatrixIdentity( &mx );
	//D3DXMatrixRotationY( &mx, D3DXToRadian(180) );
	d3ddev->SetTransform(D3DTS_WORLD, &mx);

	// ********** TEMP DRAW LINES
	//Create the lines
	Vertex Lines[] = { 
	-5.0f, -5.0f,  0.0f, D3DCOLOR_XRGB(255,0,0),
    -5.0f,  5.0f,  0.0f, D3DCOLOR_XRGB(0,0,255),
     5.0f,  5.0f,  0.0f, D3DCOLOR_XRGB(255,255,255),
     5.0f,  -5.0f, 0.0f, D3DCOLOR_XRGB(0,255,0),
    -5.0f,  -5.0f, 0.0f, D3DCOLOR_XRGB(255,0,0) };
	//Set the FVF and draw the lines
	d3ddev->SetFVF(D3DFVF_XYZ | D3DFVF_DIFFUSE);
    d3ddev->DrawPrimitiveUP(D3DPT_LINESTRIP, 4, Lines, sizeof(Vertex));
	// ********** END -- TEMP DRAW LINES


	for (UINT i = 0; i < rootFramesCount; i++)
		drawFrame(rootFrames[i]);
	

	
	d3ddev->EndScene();		// end drawing the scene
	d3ddev->Present(NULL, NULL, NULL, NULL);	// show scene
}



